package jmine.tec.web.wicket.component.menu.cfg;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.web.wicket.component.menu.MenuPanel;
import jmine.tec.web.wicket.component.menu.MenuPrincipal;
import jmine.tec.web.wicket.component.menu.WicketMenuPanel;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;

/**
 * Raiz do menu.
 * 
 * @author lundberg
 */
@XStreamAlias("menu")
public class MenuConfig implements MenuPrincipal, Serializable {

    @XStreamAsAttribute
    private String target;

    @XStreamImplicit
    private List<MenuConfigItem> children = new ArrayList<MenuConfigItem>();

    /**
     * @return the target
     */
    public String getTarget() {
        return this.target;
    }

    /**
     * @param target the target to set
     */
    public void setTarget(String target) {
        this.target = target;
    }

    /**
     * @return the children
     */
    public List<MenuConfigItem> getChildren() {
        if (this.children == null) {
            this.children = new ArrayList<MenuConfigItem>();
        }
        return this.children;
    }

    /**
     * @param children the children to set
     */
    public void setChildren(List<MenuConfigItem> children) {
        this.children = children;
    }

    /**
     * Adiciona uma página ao menu.
     * 
     * @param page page
     * @param path path
     * @return item criado
     */
    public MenuConfigItem addPage(Class<?> page, String... path) {
        return this.addPage(page, Arrays.asList(path));
    }

    /**
     * Adiciona uma página ao menu.
     * 
     * @param page page
     * @param path path
     * @return item criado
     */
    public MenuConfigItem addPage(Class<?> page, List<String> path) {
        return this.findOrCreateChild(path.get(0)).addPage(page, path.subList(1, path.size()));
    }

    /**
     * Adds an empty menu or submenu
     * 
     * @param path path
     * @return created menu
     */
    public MenuConfigItem addMenu(String... path) {
        return this.addMenu(Arrays.asList(path));
    }

    /**
     * Adds an empty menu or submenu
     * 
     * @param path path
     * @return created menu
     */
    public MenuConfigItem addMenu(List<String> path) {
        return this.findOrCreateChild(path.get(0)).addMenu(path.subList(1, path.size()));
    }

    /**
     * Busca o MenuConfigItem com o path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem findItem(String... path) {
        return this.findItem(Arrays.asList(path));
    }

    /**
     * Busca o MenuConfigItem com o path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem findItem(List<String> path) {
        return this.findChild(path.get(0)).findItem(path.subList(1, path.size()));
    }

    /**
     * Busca ou cria o item com o label.
     * 
     * @param childLabel label
     * @return item
     */
    private MenuConfigItem findOrCreateChild(String childLabel) {
        MenuConfigItem item = this.findChild(childLabel);
        if (item == null) {
            item = new MenuConfigItem();
            item.setLabel(childLabel);
            this.getChildren().add(item);
        }
        return item;
    }

    /**
     * Procura o item filho com o label, null caso não encontrado.
     * 
     * @param childLabel childLabel
     * @return MenuConfigItem
     */
    private MenuConfigItem findChild(String childLabel) {
        for (MenuConfigItem item : this.getChildren()) {
            if (item.getLabel().equals(childLabel)) {
                return item;
            }
        }
        return null;
    }

    /**
     * Funde esta configuração de menu com a outra, retornando o menu fundido.
     * 
     * @param config config
     * @return menu fundido
     */
    public MenuConfig merge(MenuConfig config) {
        if (config == null) {
            return this;
        }
        Map<String, MenuConfigItem> childMap = new HashMap<String, MenuConfigItem>();
        for (MenuConfigItem child : this.getChildren()) {
            childMap.put(child.getLabel(), child);
        }
        for (MenuConfigItem submenu : config.getChildren()) {
            String key = submenu.getLabel();
            if (childMap.containsKey(key)) {
                childMap.get(key).merge(submenu);
            } else {
                this.getChildren().add(submenu);
            }
        }
        Collections.sort(this.getChildren());
        return this;
    }

    /**
     * Ordena os itens do menu.
     */
    public void sort() {
        Collections.sort(this.getChildren());
        for (MenuConfigItem child : this.getChildren()) {
            child.sort();
        }
    }

    /**
     * {@inheritDoc}
     */
    public MenuPanel getMenuPanel(String id) {
        WicketMenuPanel newMenu = new WicketMenuPanel(id);
        for (MenuConfigItem item : this.getChildren()) {
            newMenu.addMenu(item.createSubMenu());
        }
        newMenu.build();
        return newMenu;
    }

    /**
     * Prepares to move the menu item on the given path
     * 
     * @param path path
     * @return MenuConfigMover
     */
    public MenuConfigMover move(String... path) {
        return new MenuConfigMover(this, Arrays.asList(path));
    }

    /**
     * Removes a menu item on the given path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem remove(String... path) {
        return this.remove(Arrays.asList(path));
    }

    /**
     * Removes a menu item on the given path
     * 
     * @param path path
     * @return MenuConfigItem
     */
    public MenuConfigItem remove(List<String> path) {
        if (path.size() == 1) {
            MenuConfigItem item = this.findItem(path);
            this.children.remove(item);
            return item;
        } else {
            return this.findItem(path.get(0)).remove(path.subList(1, path.size()));
        }
    }

    /**
     * Prepares to rename the item on the given path
     * 
     * @param path path
     * @return MenuConfigRenamer
     */
    public MenuConfigRenamer rename(String... path) {
        return new MenuConfigRenamer(this, Arrays.asList(path));
    }

    /**
     * Adds an item to the menu on the given path
     * 
     * @param item item
     * @param path path
     * @return MenuConfigItem
     */
    MenuConfigItem addItem(MenuConfigItem item, List<String> path) {
        if (path.isEmpty()) {
            return this.addItem(item);
        } else {
            return this.addMenu(path).addItem(item);
        }
    }

    /**
     * Adds an item to the menu root
     * 
     * @param item item
     * @return MenuConfigItem
     */
    MenuConfigItem addItem(MenuConfigItem item) {
        return this.findOrCreateChild(item.getLabel()).merge(item);
    }
}